﻿using GACore.Structures;
using System;
using System.Linq;
using System.Xml.Linq;

namespace GACore.InputArgs
{
	internal class Parser<T> where T : struct
	{
		private XElement root;

		internal Preferences GetPreferences(XElement root)
		{
			this.root = root;
			Preferences preferences = new Preferences();
			preferences.PopulationCount = GetElementAsInt(GetElementFromRoot(root, "PopulationCount"));
			preferences.BestFitness = GetElementAsDouble(GetElementFromRoot(root, "BestFitness"));
			preferences.FitnessProperties = GetFitnessProperties();
			preferences.MarriageProperties = GetMarriageProperties();
			preferences.CrossoverProperties = GetCrossoverProperties();
			preferences.MutationProperties = GetMutationProperties();
			preferences.ReplacementProperties = GetReplacementProperties();
			return preferences;
		}

		private FitnessProperties GetFitnessProperties()
		{
			XElement current = GetElementFromRoot(root, "FitnessProperties");
			FitnessFunctionType fitnessType = (FitnessFunctionType)GetElementAsEnum(typeof(FitnessFunctionType), GetElementFromRoot(current, "FitnessFunctionType"));
			bool minimizeFitnessValue = GetElementAsBool(GetElementFromRoot(current, "MinimizeFitnessValue"));
			object objectToCount = null;
			if (fitnessType == FitnessFunctionType.Count)
			{
				objectToCount = GetObjectTypeSpecific(GetElementFromRoot(current, "ObjectToCount")); ;
			}

			Vector<T> compareVector = null;
			if (fitnessType == FitnessFunctionType.EuklidDistance || fitnessType == FitnessFunctionType.HammingDistance || (fitnessType == FitnessFunctionType.Individual && current.Element("CompareVector") != null))
			{
				compareVector = GetCompareVector(current);
			}

			return new FitnessProperties(fitnessType, minimizeFitnessValue, objectToCount, compareVector);
		}

		private MarriageProperties GetMarriageProperties()
		{
			XElement current = GetElementFromRoot(root, "MarriageProperties");
			SelectionProfiles selectionSchema = (SelectionProfiles)GetElementAsEnum(typeof(SelectionProfiles), GetElementFromRoot(current, "SelectionSchema"));
			SelectionProfiles marriageSchema = (SelectionProfiles)GetElementAsEnum(typeof(SelectionProfiles), GetElementFromRoot(current, "MarriageSchema"));
			int countOfSelectedVectors = 0;
			if (selectionSchema == SelectionProfiles.BestFitness || selectionSchema == SelectionProfiles.Random)
			{
				countOfSelectedVectors = GetElementAsInt(GetElementFromRoot(current, "CountOfSelectedVectors"));
			}

			int[][] marriageProfile = null;
			if (selectionSchema == SelectionProfiles.DefinedMatrix)
			{
				marriageProfile = GetMarriageProfile(current, marriageProfile);
			}

			return new MarriageProperties(selectionSchema, marriageSchema, countOfSelectedVectors, marriageProfile);
		}

		private static int[][] GetMarriageProfile(XElement current, int[][] marriageProfile)
		{
			try
			{
				return (from row in current.Element("MarriageProfile").Elements("row")
						select (from e in row.Elements("element")
								select int.Parse((string)e.Value)).ToArray()).ToArray();
			}
			catch
			{
				throw new ArgumentException("Das Element " + current.Name + " kann nicht in das Heiratsschema überführt werden.");
			}
		}

		private CrossoverProperties GetCrossoverProperties()
		{
			XElement current = GetElementFromRoot(root, "CrossoverProperties");
			CrossoverSchema schema = (CrossoverSchema)GetElementAsEnum(typeof(CrossoverSchema), GetElementFromRoot(current, "CrossoverSchema"));

			int[] points = null;
			if (schema == CrossoverSchema.Crossover1Point || schema == CrossoverSchema.Crossover2Point)
			{
				points = GetCrossoverPoints(current, points);
			}
			return new CrossoverProperties(schema, points);
		}

		private static int[] GetCrossoverPoints(XElement current, int[] points)
		{
			try
			{
				return (from p in current.Element("CrossoverPoints").Elements("element")
						select int.Parse((string)p.Value)).ToArray();
			}
			catch
			{
				throw new ArgumentException("Aus dem Element " + current.Name + " kann nicht abgeleitet werden, wo der Crossover erfolgen soll.");
			}
		}

		private MutationProperties GetMutationProperties()
		{
			XElement current = GetElementFromRoot(root, "MutationProperties");
			double mutationRate = GetElementAsDouble(GetElementFromRoot(current, "MutationRate"));
			object mutationWidth = null;
			if (mutationRate > 0)
			{
				mutationWidth = GetObjectTypeSpecific(GetElementFromRoot(current, "MutationWidth"));
			}

			MutationScope scope = (MutationScope)GetElementAsEnum(typeof(MutationScope), GetElementFromRoot(current, "MutationScope"));

			object min = GetMinValue();
			object max = GetMaxValue();
			XElement tmpXEl = GetElementFromRoot(current, "Limits");
			if (mutationRate > 0 && tmpXEl != null)
			{
				var minXEl = tmpXEl.Element("Min");
				var maxXel = tmpXEl.Element("Max");

				if (minXEl != null)
				{
					min = GetObjectTypeSpecific(minXEl);
				}
				if (maxXel != null)
				{
					max = GetObjectTypeSpecific(maxXel);
				}
			}
			return new MutationProperties(mutationRate, mutationWidth, scope, min, max);
		}

		private ReplacementProperties GetReplacementProperties()
		{
			XElement current = GetElementFromRoot(root, "ReplacementProperties");
			ReplacementSchema schema = (ReplacementSchema)GetElementAsEnum(typeof(ReplacementSchema), GetElementFromRoot(current, "ReplacementSchema"));
			int replacementCount = 0;
			if (schema == ReplacementSchema.DeleteNLast || schema == ReplacementSchema.StandardElitistic)
			{
				replacementCount = GetElementAsInt(GetElementFromRoot(current, "ReplacementCount"));
			}

			return new ReplacementProperties(schema, replacementCount);
		}

		#region Helper

		private XElement GetElementFromRoot(XElement root, string elementName)
		{
			XElement current = root.Element(elementName);
			if (current == null)
			{
				throw new ArgumentException("Das Element mit dem Namen " + elementName + " ist nicht vorhanden.");
			}
			return current;
		}

		private int GetElementAsInt(XElement element)
		{
			try
			{
				return (int)element;
			}
			catch
			{
				throw new ArgumentException("Das Element " + element.Name + " ist kein Integer.");
			}
		}

		private double GetElementAsDouble(XElement element)
		{
			try
			{
				return double.Parse((string)element);
			}
			catch
			{
				throw new ArgumentException("Das Element " + element.Name + " ist kein Double.");
			}
		}

		private bool GetElementAsBool(XElement element)
		{
			try
			{
				return (bool)element;
			}
			catch
			{
				throw new ArgumentException("Das Element " + element.Name + " ist kein Boolean.");
			}
		}

		private object GetElementAsEnum(Type type, XElement element)
		{
			try
			{
				return Enum.Parse(type, (string)element);
			}
			catch
			{
				throw new ArgumentException("Das Element " + element.Name + " kann nicht in das Enum " + type.ToString() + " überführt werden.");
			}
		}

		private object GetObjectTypeSpecific(XElement xElement)
		{
			try
			{
				object ret = null;
				if (typeof(T) == typeof(bool))
				{
					ret = (bool)xElement;
				}
				else if (typeof(T) == typeof(int))
				{
					ret = (int)xElement;
				}
				else if (typeof(T) == typeof(double))
				{
					ret = double.Parse((string)xElement);
				}
				return ret;
			}
			catch
			{
				throw new ArgumentException("Der Datentyp im Element " + xElement.Name + " ist nicht korrekt.");
			}
		}

		private Vector<T> GetCompareVector(XElement current)
		{
			try
			{
				var compareElements = current.Element("CompareVector").Elements("element");
				Vector<T> compareVector = new Vector<T>(compareElements.Count(), null, null);

				T[] vectorElements = (from e in current.Element("CompareVector").Elements("element")
									  select (T)GetObjectTypeSpecific(e)).ToArray();
				compareVector.Elements = vectorElements;

				return compareVector;
			}
			catch
			{
				throw new ArgumentException("Aus dem Element " + current.Name + " kann kein Vergleichsvektor erstellt werden.");
			}
		}

		private T GetMinValue()
		{
			if (typeof(T) == typeof(double))
			{
				return (T)(object)double.MinValue;
			}
			else if (typeof(T) == typeof(int))
			{
				return (T)(object)int.MinValue;
			}
			else if (typeof(T) == typeof(bool))
			{
				return (T)(object)false;
			}
			throw new NotImplementedException("Der Vektortyp ist nicht implementiert.");
		}

		private T GetMaxValue()
		{
			if (typeof(T) == typeof(double))
			{
				return (T)(object)double.MaxValue;
			}
			else if (typeof(T) == typeof(int))
			{
				return (T)(object)int.MaxValue;
			}
			else if (typeof(T) == typeof(bool))
			{
				return (T)(object)true;
			}
			throw new NotImplementedException("Der Vektortyp ist nicht implementiert.");
		}

		#endregion
	}
}
